#include "SocketOps.h"

int sockets::getSocketError(int sockfd)
{
    int optval;
    socklen_t optlen = sizeof optval;
    if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0)
    {
        return errno;
    }
    else
    {
        return optval;
    }
}

ssize_t sockets::write(int fd, const void *buf, size_t len)
{
    return ::write(fd, buf, len);
}

ssize_t sockets::read(int fd, void *buf, size_t len)
{
    return ::read(fd, buf, len);
}

ssize_t sockets::readv(int fd, iovec *vec, size_t len)
{
    return ::readv(fd, vec, len);
}

int sockets::createNonblockingOrDie(sa_family_t family)
{
    int sockfd = ::socket(family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_TCP);
    if (sockfd < 0)
    {
        assert(sockfd > 0);
    }
    return sockfd;
}

int sockets::connect(int sockfd, const sockaddr_in &addr)
{
    return ::connect(sockfd, (sockaddr *)(&addr), sizeof(sockaddr_in));
}

void sockets::listen(int listenFd, int n)
{
    int ret = ::listen(listenFd, n);
    assert(ret >= 0);
}

int sockets::accept(int sockfd, sockaddr_in *addr)
{
    socklen_t socklen = sizeof(sockaddr_in);
    int connfd = ::accept4(sockfd, (sockaddr *)(&addr), &socklen, SOCK_NONBLOCK | SOCK_CLOEXEC);
    if (connfd < 0)
    {
        int savedErrno = errno;
        std::cout << "Socket::accept" << std::endl;
        switch (savedErrno)
        {
        case EAGAIN:
        case ECONNABORTED:
        case EINTR:
        case EPROTO: // ???
        case EPERM:
        case EMFILE: // per-process lmit of open file desctiptor ???
            // expected errors
            errno = savedErrno;
            break;
        case EBADF:
        case EFAULT:
        case EINVAL:
        case ENFILE:
        case ENOBUFS:
        case ENOMEM:
        case ENOTSOCK:
        case EOPNOTSUPP:
            // unexpected errors
            std::cout << "unexpected error of ::accept " << savedErrno << std::endl;
            break;
        default:
            std::cout << "unknown error of ::accept " << savedErrno << std::endl;
            break;
        }
    }
    return connfd;
}

void sockets::bind(int sockfd, const sockaddr_in &addr)
{
    int ret = ::bind(sockfd, (sockaddr *)(&addr), sizeof(sockaddr_in));
    if (ret < 0)
    {
        std::cout << "bind failed: " << strerror(errno) << std::endl;
    }
}

void sockets::shutdownWrite(int sockfd)
{
    if (::shutdown(sockfd, SHUT_WR) < 0)
    {
        std::cout << "shutdown failed: " << strerror(errno) << std::endl;
    }
}

void sockets::close(int sockfd)
{
    ::close(sockfd);
}

sockaddr_in sockets::getLocalAddr(int sockfd)
{
    sockaddr_in addr;
    socklen_t socklen = sizeof(addr);
    ::memset(&addr, 0, socklen);
    if (::getsockname(sockfd, (sockaddr *)(&addr), &socklen) < 0)
    {
        std::cout << "getsockname failed!" << std::endl;
    }
    return addr;
}

sockaddr_in sockets::getPeerAddr(int sockfd)
{
    sockaddr_in addr;
    socklen_t socklen = sizeof(addr);
    ::memset(&addr, 0, socklen);
    if (::getpeername(sockfd, (sockaddr *)(&addr), &socklen) < 0)
    {
        std::cout << "getsockname failed!" << std::endl;
    }
    return addr;
}
